package core

import (
	"fmt"
	"math/rand"
	"sync"
	"zinx/MMO_Game_Zinx/pb"
	"zinx/Zinx/ziface"

	"google.golang.org/protobuf/proto"
)

// 玩家模块
type Player struct {
	Pid  int32              // 玩家ID
	Conn ziface.IConnection // 与客户端的连接
	X    float32            // 平面x
	Y    float32            // 高度
	Z    float32            // 平面y
	V    float32            // 旋转0-360°
}

// 生成玩家ID
var PidGen int32 = 1
var PidLock sync.Mutex

// 初始化玩家模块
func NewPlayer(conn ziface.IConnection) *Player {
	// 生成玩家ID
	PidLock.Lock()
	defer PidLock.Unlock()
	id := PidGen
	PidGen++
	return &Player{
		Pid:  id,
		Conn: conn,
		X:    float32(160 + rand.Intn(10)),
		Y:    0,
		Z:    float32(140 + rand.Intn(20)),
		V:    0,
	}
}

// 给客户端发送消息, 将数据protobuf序列化后, 封装进zinx.SendMsg
func (p *Player) SendMsg(msgID uint32, data proto.Message) {
	// 将proto Message序列化
	msg, err := proto.Marshal(data)
	if err != nil {
		fmt.Println("[Player]Marshal err: ", err)
		return
	}
	// 将序列化后的二进制用zinx.SendMsg发送给客户端
	if p.Conn == nil {
		fmt.Println("[Player]Connection is broken")
		return
	}
	if err := p.Conn.SendMsg(msgID, msg); err != nil {
		fmt.Println("[Player]SendMsg err: ", err)
		return
	}
}

// 返回当前玩家九宫格players
func (p *Player) GetSurroundPlayers() []*Player {
	gid := WMObj.AOI.GetGIDByPos(p.X, p.Z)
	pids := WMObj.AOI.GetSurroundPlayerIDsByGID(gid)
	players := make([]*Player, 0, len(pids))
	for _, pid := range pids {
		players = append(players, WMObj.GetPlayerByPid(int32(pid)))
	}
	return players
}

// ----上线----

// 同步玩家ID msgID=1
func (p *Player) SyncPid() {
	// 初始化Message SyncPid
	proto_msg := &pb.SyncPid{
		Pid: p.Pid,
	}
	// 发送给客户端
	p.SendMsg(1, proto_msg)
}

// 广播消息 msgID=200 Tp=2 同步初始坐标
func (p *Player) BroadCastStartPosition() {
	// 初始化Message BroadCast
	proto_msg := &pb.BroadCast{
		Pid: p.Pid,
		Tp:  2,
		Data: &pb.BroadCast_P{
			P: &pb.Position{
				X: p.X,
				Y: p.Y,
				Z: p.Z,
				V: p.V,
			},
		},
	}
	// 发送给客户端
	p.SendMsg(200, proto_msg)
}

// 玩家上线
func (p *Player) Online() {
	// 同步玩家ID msgID=1
	p.SyncPid()
	// 广播消息 msgID=200 Tp=2同步初始坐标
	p.BroadCastStartPosition()
	// 添加到World中
	WMObj.AddPlayer(p)
	// 数据同步给周围玩家 msgID=202 + msgID=200 Tp=2
	p.SyncPlayers()
}

// ----上线----
// ----下线----

func (p *Player) Offline() {
	// 返回当前玩家九宫格players
	players := p.GetSurroundPlayers()
	// 广播消息 msgID=201
	proto_msg := &pb.SyncPid{
		Pid: p.Pid,
	}
	for _, player := range players {
		player.SendMsg(201, proto_msg)
	}
	// 在AOI管理器中删除玩家
	gid := WMObj.AOI.GetGIDByPos(p.X, p.Z)
	WMObj.AOI.RemovePlayerIDToGrid(int(p.Pid), gid)
	// 在世界管理器中删除玩家
	WMObj.RemovePlayer(p)
}

// ----下线----

// 广播消息 msgID=200 Tp=1 世界聊天
func (p *Player) BroadCastTalk(content string) {
	proto_msg := &pb.BroadCast{
		Pid: p.Pid,
		Tp:  1,
		Data: &pb.BroadCast_Content{
			Content: content,
		},
	}
	// 发送给所有客户端
	players := WMObj.GetAllPlayers()
	for _, player := range players {
		player.SendMsg(200, proto_msg)
	}
}

// 数据同步给周围玩家 msgID=202 + msgID=200 Tp=2
func (p *Player) SyncPlayers() {
	// 获取当前玩家九宫格Players
	players := p.GetSurroundPlayers()
	// 同步自己坐标给其他玩家 msgID=200 Tp=2
	proto_msg200 := &pb.BroadCast{
		Pid: p.Pid,
		Tp:  2,
		Data: &pb.BroadCast_P{
			P: &pb.Position{
				X: p.X,
				Y: p.Y,
				Z: p.Z,
				V: p.V,
			},
		},
	}
	for _, player := range players {
		player.SendMsg(200, proto_msg200)
	}
	// 同步其他玩家数据 msgID=202
	proto_players := make([]*pb.Player, 0, len(players))
	for _, player := range players {
		proto_player := &pb.Player{
			Pid: player.Pid,
			P: &pb.Position{
				X: player.X,
				Y: player.Y,
				Z: player.Z,
				V: player.V,
			},
		}
		proto_players = append(proto_players, proto_player)
	}
	proto_msg202 := &pb.SyncPlayers{
		Ps: proto_players[:],
	}
	p.SendMsg(202, proto_msg202)
}

// 广播消息 更新当前玩家坐标 并同步给其他玩家 msgID=200 Tp=4
func (p *Player) UpdatePos(x, y, z, v float32) {
	// 更新当前玩家坐标
	p.X, p.Y, p.Z, p.V = x, y, z, v
	// 获取当前玩家九宫格Players
	players := p.GetSurroundPlayers()
	// 同步自己坐标给其他玩家 msgID=200 Tp=4
	proto_msg := &pb.BroadCast{
		Pid: p.Pid,
		Tp:  4,
		Data: &pb.BroadCast_P{
			P: &pb.Position{
				X: p.X,
				Y: p.Y,
				Z: p.Z,
				V: p.V,
			},
		},
	}
	for _, player := range players {
		player.SendMsg(200, proto_msg)
	}
}
